package org.jboss.seam.core;

import static org.jboss.seam.annotations.Install.BUILT_IN;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import javax.el.PropertyNotWritableException;
import javax.el.ValueExpression;

import org.apache.commons.lang.StringUtils;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.validator.ExtendedClassValidator;
import org.hibernate.validator.InvalidValue;
import org.jboss.seam.Component;
import org.jboss.seam.Instance;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.el.EL;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.persistence.ExtendedHibernateSessionFactory;

/**
 * Caches instances of Hibernate Validator ExtendedClassValidator
 * 
 * @author Spazzoli Nicola
 *
 */
@Name("org.jboss.seam.core.validators")
@BypassInterceptors
@Scope(ScopeType.APPLICATION)
@Install(precedence = BUILT_IN, classDependencies="org.hibernate.validator.ExtendedClassValidator")
public class ExtendedValidators extends Validators
{
	private static final LogProvider log = Logging.getLogProvider(ExtendedValidators.class);


	private Map<Key, ExtendedClassValidator> classValidators = new ConcurrentHashMap<Key, ExtendedClassValidator>();


	/**
	 * Estendo la classe interna Key. 
	 * Includo anche il sessionFactory come chiave.
	 * 
	 * @author Nicola Spazzoli
	 *
	 */
	class Key extends org.jboss.seam.core.Validators.Key
	{
		private SessionFactory sessionfactory;

		public Key(Class validatableClass, java.util.Locale locale, SessionFactory sessionFactory)
		{
			super(validatableClass, locale);
			this.sessionfactory=sessionFactory;
		}

		public SessionFactory getSessionfactory() {
			return sessionfactory;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = super.hashCode();
			result = prime * result + getOuterType().hashCode();
			result = prime
			* result
			+ ((sessionfactory == null) ? 0 : sessionfactory.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (!super.equals(obj))
				return false;
			if (getClass() != obj.getClass())
				return false;
			Key other = (Key) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (sessionfactory == null) {
				if (other.sessionfactory != null)
					return false;
			} else if (!sessionfactory.equals(other.sessionfactory))
				return false;
			return true;
		}

		private ExtendedValidators getOuterType() {
			return ExtendedValidators.this;
		}
	}


	/**
	 * Get the cached ExtendedClassValidator instance. If the
	 * argument is an instance of a session bean Seam 
	 * component instance, the returned validator will 
	 * be aware of constraints defined on the bean class.
	 * Therefore this method is preferred to
	 * getValidator(Class) if the argument might be a
	 * session bean.
	 * 
	 * @param model the object to be validated
	 */
	public <T> ExtendedClassValidator<T> getExtendedValidator(T model) {
		Class modelClass = model instanceof Instance ?
				( (Instance) model ).getComponent().getBeanClass() :
					model.getClass();
				return getExtendedValidator( (Class<T>) modelClass );
	}

	//BEGIN: Nicola Spazzoli
	/**
	 * Metodo che implementa la ricerca del sessionFactory sul quale operare
	 */
	private SessionFactory findFactory(Class<?> modelClass, String factoryName){
		log.info("Searching factory...");
		if ( !StringUtils.isBlank(factoryName))
			return (SessionFactory)  Component.getInstance(factoryName);
		//	   Component.getInstance("hibernateSessionFactory");
		//	   Collection<Class> c=Component.getBusinessInterfaces(SessionFactory.class);
		//	   Object o=Contexts.getApplicationContext().getNames();
		List<SessionFactory> list=ExtendedHibernateSessionFactory.getSessionFactoryList();
		SessionFactory founded=null;
		//se c'è un solo sessionFactory è l'unico che posso restituire
		if (list.size()==1)
			founded=list.get(0);
		else {
			//cerco fra tutti i sessionFactory il modelClass
			List<SessionFactory> potenzialSessionFactory=new ArrayList<SessionFactory>();
			for (SessionFactory sessionFactory : list) {
				ClassMetadata metadata=null;
				metadata= sessionFactory.getClassMetadata(modelClass);

				//			   ClassMetadata prova=sessionFactory.getClassMetadata(it.regulus.webkom.model.pojo.User.class);
				if (metadata!=null)
					potenzialSessionFactory.add(sessionFactory);
			}

			if (potenzialSessionFactory.size()<=0) {
				//se non ne ho trovati lancio errore!!!
				throw new RuntimeException();
			}

			if (potenzialSessionFactory.size()==1) {
				//se ne ho trovato uno solo allora è per forza quello corretto
				founded=potenzialSessionFactory.get(0);
			}
			//se ne ho trovati 2 o più -> DEVO SCEGLIERE
			for (SessionFactory sessionFactory : potenzialSessionFactory) {
				founded=potenzialSessionFactory.get(0);
			}
			//			   Component.getInstance("secondoFactory");

		}
		log.info("Founded factory="+founded);
		return founded;
	}

	/**
	 * Get the cached ExtendedClassValidator instance.
	 * 
	 * @param modelClass the class to be validated
	 */
	public <T> ExtendedClassValidator<T> getExtendedValidator(Class<T> modelClass)
	{
		java.util.ResourceBundle bundle = SeamResourceBundle.getBundle();
		SessionFactory selectedFactory=findFactory(modelClass, null);
		assert(selectedFactory!=null);

		Locale none = bundle == null ? new Locale("NONE") : bundle.getLocale();
		Key key = new Key(modelClass, none, selectedFactory);
		ExtendedClassValidator result = classValidators.get(key);

		if (result == null)	{
			result =  createExtendedValidator(modelClass, selectedFactory);
			result.setSessionFactory(selectedFactory);
			classValidators.put(key, result);
		}
		return result;
	}
	//END: Nicola Spazzoli

	/**
	 * Create a new ClassValidator for the given class, using the current Seam
	 * ResourceBundle.
	 * 
	 * @param modelClass the class to be validated
	 */
	@SuppressWarnings("unchecked")
	protected <T> ExtendedClassValidator<T> createExtendedValidator(Class<T> modelClass, SessionFactory sessionFactory) {
		java.util.ResourceBundle bundle = SeamResourceBundle.getBundle();

		return bundle == null ? new ExtendedClassValidator(modelClass, sessionFactory) : new ExtendedClassValidator(modelClass, bundle, sessionFactory);
	}


	/**
	 * Validate that the given value can be assigned to the property given by the value
	 * expression.
	 * 
	 * @param valueExpression a value expression, referring to a property
	 * @param elContext the ELContext in which to evaluate the expression
	 * @param value a value to be assigned to the property
	 * @return a set of potential InvalidValues, from Hibernate Validator
	 */
	public InvalidValue[] validate(ValueExpression valueExpression, ELContext elContext, Object value)
	{
		ValidatingResolver validatingResolver = new ValidatingResolver( elContext.getELResolver() );
		ELContext decoratedContext = EL.createELContext(elContext, validatingResolver);
		valueExpression.setValue(decoratedContext, value);
		return validatingResolver.getInvalidValues();
	}


	class ValidatingResolver extends org.jboss.seam.core.Validators.ValidatingResolver	{
		private InvalidValue[] invalidValues;

		public ValidatingResolver(ELResolver delegate) {
			super(delegate);
		}

		public InvalidValue[] getInvalidValues()
		{
			return invalidValues;
		}

		@Override
		public void setValue(ELContext context, Object base, Object property, Object value) 
		throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException {
			if (base!=null && property!=null ) {
				context.setPropertyResolved(true);
				invalidValues = getExtendedValidator(base).getPotentialInvalidValues( property.toString(), value, base );
			}
		}
	}

	public static ExtendedValidators instance()	{
		if (!Contexts.isApplicationContextActive())	{
			throw new IllegalStateException("No active application scope");
		}
		return (ExtendedValidators) Component.getInstance(ExtendedValidators.class, ScopeType.APPLICATION);
	}

}
